大家一定會覺得這個標題很奇怪,不就都是 new Vue
嗎?是有什麼差別?對啊,其實都是 Vue 沒錯,但是就像是斯斯有兩種,Vue 也是有好多種不同面向。
是說現在斯斯好像不只兩種了。
通常,我們在實作一個 App 的時候,最終我們使用了 new Vue
來將整個 App 綁定在某一個 DOM 的元件上。如果,你綁定一次不夠,想要綁定兩次呢?也不是不可以,
import Vue from 'vue'
import App1 from './App1.vue'
import App2 from './App2.vue'
new Vue({
render: h => h(App1)
}).$mount('#app1')
new Vue({
render: h => h(App2)
}).$mount("#app2')
雖然不是很好的例子,但實際上也是可以這樣操作的。但是,基於什麼樣的理由需要這樣操作呢?這樣操作的情況下,又會衍生出什麼狀況?
倘若,你將這兩個 App 分成兩個入口,也就是 main1.js
與 main2.js
的話,還會有更多問題:
window
這一層。現在會有多少人這樣做呢?
沒有(吧)?
主要的命題在此。我們不一定每次經手一個專案,都要做一大堆前置作業。那麼,在沒有這些工具的情況下,我們是不是就沒辦法寫 Vue 了?
對啊(欸等等)!
最簡單的方式,我們這樣就可以開始使用 Vue 了,
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.0"></script>
然後你的寫法就會變成,
new Vue({
template: '<div>{{ msg }}</div>',
data: function () {
return {
msg: 'HelloWorld'
}
}
})
然後就覺得,我靠!這樣超難寫的!我要回去用 Vue CLI 建立環境了(住手!) 我們這邊的大前提就是,在 沒有 Webpack 這一類的工具下,所以神說沒有 Webpack 就是沒有 Webpack,沒得含扣。
所以,在這種 極端 環境底下,我們到底要怎麼製作 Vue App?我們該怎麼製作我們的元件?
放棄不可恥,但是有用!
首先,你必須要知道幾件事情:
我們可以直接用例子來看,
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>ITHome 2019</title>
</head>
<body>
<div id="app">
<h1>{{ msg }}</h1>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.0"></script>
<script>
(function() {
new Vue({
data: function () {
return {
msg: 'Hello World'
}
}
}).$mount('#app');
})();
</script>
</body>
</html>
這樣看起來好像沒什麼大問題,如果我們有其他的元件要載入使用呢?
Vue.component(
'MyComponent',
{
name: 'MyComponent',
template: '<div class="my-component">{{ msg }}</div>',
data: function () {
return {
msg: 'Hello My Component'
}
}
}
)
然後我們就能在 HTML 裡面使用這個 <my-component></my-component>
元件了。
我們又要來聊元件載入的問題了。上述的例子你會發現 template
的地方是直接寫在 <script>
裡面,那麼,我們每次要更新的時候,是不是就變得相當麻煩。那麼我們可以怎麼做呢?
template
的部分分開來寫,寫到一個 .html
檔案裡面。.html
的資料,把讀取的資料放到 template
裡面。具體的方式怎麼做呢?首先,我們可以拿 axios
這個工具來用,
axios({
method: 'get',
url: './templates/mycomponent.html',
responseType: 'text'
}).then(function (res) {
Vue.component(
'MyComponent',
{
template: res.data,
data: function () {
return {
msg: 'Hello My Component'
}
}
}
)
})
然後這個檔案我們把他儲存成 ./js/mycomponent.js
之後,再把他放到 index.html
裡面。然後原本的 template
的部分,將他放到 ./templates/mycomponent.html
,這樣我們就不需要改 JavaScript 的檔案,直接改樣版檔案即可。
有沒有 MVC 的錯覺。
所以,我們一開始可能會是這樣,
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>ITHome 2019</title>
</head>
<body>
<div id="app">
<h1>{{ msg }}</h1>
<my-component></my-component>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.0"></script>
<script src="./js/mycomponent.js"></script>
<script>
(function () {
new Vue({
data: function () {
return {
msg: 'Hello World'
}
}
}).$mount('#app');
})();
</script>
</body>
</html>
這個時候你再回到瀏覽器,發現 my-component
怎麼不會出現?原因在於你的 mycomponent.js
當中,是使用 XHR 的方式來讀取樣版檔案。所以,當你的 App 在啟動的時候,你的 XHR 可能還在讀取,所以這個時候,你的 my-component
其實並沒有在 Vue 的全域元件當中。
這樣一來,你就必須要先確保樣版檔案已經讀取進來了,才能將你的 App 啟動。這個時候,我們可以利用 Vue 自身,製作一個 EventBus 來當作監聽工具。所以,我們這個監聽工具需要做哪些事情呢?
componentLoaded
代表元件被讀取進來了。window.EventBus = new Vue();
window.EventBus.$on(
'componentLoaded',
function () {
new Vue({
data: function () {
return {
msg: 'Hello World'
}
}
}).$mount('#app');
}
);
然後我們的 mycomponent.js
在讀取完樣版之後,要觸發 我已經讀取完畢 的動作。
// ...前略
window.EventBus.$emit('componentLoaded');
這麼一來,我們的元件看起來就很正常顯示了。
那麼,如果我們有很多元件,巢狀元件的時候該怎麼辦?
Promise.all
是你的好伙伴。
也許你們會覺得很奇怪,為何會有這種奇怪的需求?就如同我剛剛的大前提,當你沒有 Webpack 的時候,這就是一種比較詭異,但是還算是合理的解決方案。只是說,如果我是開發完之後,把 dist
給客戶就好,那麼,好像也不必真的那麼麻煩。
其實在這個系列文後面還是有提到動態載入的事情,只是最近這幾篇文章好像都快講完了?
事情當然不是你們所想的那麼簡單
雖然說我後面會提到 Vue-Router 的部分,但是,這邊先偷偷給大家看一個,搭配 Router 動態載入的實例:
let router = new VueRouter({
routes: [
{
path: '/',
name: 'homepage',
component: { template: '<router-homepage></router-homepage>' },
props: true,
meta: {
name: '首頁'
}
}
]
});
router.beforeEach(function(to, from, next) {
let template = 'not-found';
if (typeof to.name !== 'undefined' && to.name !== null) {
template = to.name;
}
return generateTemplateLoader(template).then(function() {
next();
});
});
至於那個 generateTemplateLoader
的地方呢?我們這邊就賣個關子吧。等到大概第 21, 22 篇的時候我們再來聊聊。
或許你會覺得,怎麼一直都在講動態載入。我只能說,一旦遇上了你還是得想出各種花招來滿足一些需求。然後,這些所謂 動態載入 都是從奇怪的需求衍生而來的。
可以的話,我也想每次都 VUE CLI 來做啊(苦笑)。
樓主好,我最近剛好有這個需求也恰好看到你這篇文章剛剛也嘗試成功了,但關於多元件載入的部分使用promise.all()這部分不知道是否也是在index.html中的script撰寫,不曉得是否有範例呢?
因為現在我的理解是不知道js中的window.EventBus.$emit('componentLoaded');是否能重複利用呢?